{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Using Layout Templates\n",
    "\n",
    "As we showed in [Layout and Styling of Jupyter widgets](Widget%20Styling.ipynb) multiple widgets can be aranged together using the flexible [GridBox](Widget%20Styling.ipynb#The-Grid-Layout) specification. However, use of the specification involves some understanding of CSS properties and may impose sharp learning curve. Here, we will describe layout templates built on top of `GridBox` that simplify creation of common widget layouts."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Utils widgets\n",
    "from ipywidgets import Button, Layout, jslink, IntText, IntSlider\n",
    "\n",
    "def create_expanded_button(description, button_style):\n",
    "    return Button(description=description, button_style=button_style, layout=Layout(height='auto', width='auto'))\n",
    "\n",
    "top_left_button = create_expanded_button(\"Top left\", 'info')\n",
    "top_right_button = create_expanded_button(\"Top right\", 'success')\n",
    "bottom_left_button = create_expanded_button(\"Bottom left\", 'danger')\n",
    "bottom_right_button = create_expanded_button(\"Bottom right\", 'warning')\n",
    "\n",
    "top_left_text = IntText(description='Top left', layout=Layout(width='auto', height='auto'))\n",
    "top_right_text = IntText(description='Top right', layout=Layout(width='auto', height='auto'))\n",
    "bottom_left_slider = IntSlider(description='Bottom left', layout=Layout(width='auto', height='auto'))\n",
    "bottom_right_slider = IntSlider(description='Bottom right', layout=Layout(width='auto', height='auto'))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2x2 Grid"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can easily create a layout with 4 widgets aranged on 2x2 matrix using the `TwoByTwoLayout` widget: "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from ipywidgets import TwoByTwoLayout\n",
    "\n",
    "TwoByTwoLayout(top_left=top_left_button,\n",
    "               top_right=top_right_button,\n",
    "               bottom_left=bottom_left_button,\n",
    "               bottom_right=bottom_right_button)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If you don't define a widget for some of the slots, the layout will automatically re-configure itself by merging neighbouring cells"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "TwoByTwoLayout(top_left=top_left_button,\n",
    "               bottom_left=bottom_left_button,\n",
    "               bottom_right=bottom_right_button)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can pass `merge=False` in the argument of the `TwoByTwoLayout` constructor if you don't want this behavior"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "TwoByTwoLayout(top_left=top_left_button,\n",
    "               bottom_left=bottom_left_button,\n",
    "               bottom_right=bottom_right_button,\n",
    "               merge=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can add a missing widget even after the layout initialization:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "layout_2x2 = TwoByTwoLayout(top_left=top_left_button,\n",
    "                            bottom_left=bottom_left_button,\n",
    "                            bottom_right=bottom_right_button)\n",
    "layout_2x2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "layout_2x2.top_right = top_right_button"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can also use the linking feature of widgets to update some property of a widget based on another widget:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "app = TwoByTwoLayout(top_left=top_left_text, top_right=top_right_text,\n",
    "                     bottom_left=bottom_left_slider, bottom_right=bottom_right_slider)\n",
    "                     \n",
    "link_left = jslink((app.top_left, 'value'), (app.bottom_left, 'value'))\n",
    "link_right = jslink((app.top_right, 'value'), (app.bottom_right, 'value'))\n",
    "app.bottom_right.value = 30\n",
    "app.top_left.value = 25\n",
    "app"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can easily create more complex layouts with custom widgets. For example, you can use [bqplot](http://github.com/bloomberg/bqplot) Figure widget to add plots:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import bqplot as bq\n",
    "import numpy as np"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "size = 100\n",
    "np.random.seed(0)\n",
    "\n",
    "x_data = range(size)\n",
    "y_data = np.random.randn(size)\n",
    "y_data_2 = np.random.randn(size)\n",
    "y_data_3 = np.cumsum(np.random.randn(size) * 100.)\n",
    "\n",
    "x_ord = bq.OrdinalScale()\n",
    "y_sc = bq.LinearScale()\n",
    "\n",
    "bar = bq.Bars(x=np.arange(10), y=np.random.rand(10), scales={'x': x_ord, 'y': y_sc})\n",
    "ax_x = bq.Axis(scale=x_ord)\n",
    "ax_y = bq.Axis(scale=y_sc, tick_format='0.2f', orientation='vertical')\n",
    "\n",
    "fig = bq.Figure(marks=[bar], axes=[ax_x, ax_y], padding_x=0.025, padding_y=0.025,\n",
    "                layout=Layout(width='auto', height='90%'))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from ipywidgets import FloatSlider\n",
    "\n",
    "max_slider = FloatSlider(min=0, max=10, default_value=2, description=\"Max: \",\n",
    "                         layout=Layout(width='auto', height='auto'))\n",
    "min_slider = FloatSlider(min=-1, max=10, description=\"Min: \",\n",
    "                         layout=Layout(width='auto', height='auto'))\n",
    "app = TwoByTwoLayout(top_left=min_slider,\n",
    "                     bottom_left=max_slider, \n",
    "                     bottom_right=fig,\n",
    "                     align_items=\"center\", \n",
    "                     height='700px')\n",
    "\n",
    "jslink((y_sc, 'max'), (max_slider, 'value'))\n",
    "jslink((y_sc, 'min'), (min_slider, 'value'))\n",
    "jslink((min_slider, 'max'), (max_slider, 'value'))\n",
    "jslink((max_slider, 'min'), (min_slider, 'value'))\n",
    "\n",
    "max_slider.value = 1.5\n",
    "app"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## AppLayout"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`AppLayout` is a widget layout template that allows you to create an application-like widget arrangements. It consist of a header, a footer, two sidebars and a central pane:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from ipywidgets import AppLayout, Button, Layout"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "header_button = create_expanded_button('Header', 'success')\n",
    "left_button = create_expanded_button('Left', 'info')\n",
    "center_button = create_expanded_button('Center', 'warning')\n",
    "right_button = create_expanded_button('Right', 'info')\n",
    "footer_button = create_expanded_button('Footer', 'success')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "AppLayout(header=header_button,\n",
    "          left_sidebar=left_button,\n",
    "          center=center_button,\n",
    "          right_sidebar=right_button,\n",
    "          footer=footer_button)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "However with the automatic merging feature, it's possible to achieve many other layouts:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "AppLayout(header=None,\n",
    "          left_sidebar=None,\n",
    "          center=center_button,\n",
    "          right_sidebar=None,\n",
    "          footer=None)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "AppLayout(header=header_button,\n",
    "          left_sidebar=left_button,\n",
    "          center=center_button,\n",
    "          right_sidebar=right_button,\n",
    "          footer=None)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "AppLayout(header=None,\n",
    "          left_sidebar=left_button,\n",
    "          center=center_button,\n",
    "          right_sidebar=right_button,\n",
    "          footer=None)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "AppLayout(header=header_button,\n",
    "          left_sidebar=left_button,\n",
    "          center=center_button,\n",
    "          right_sidebar=None,\n",
    "          footer=footer_button)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "AppLayout(header=header_button,\n",
    "          left_sidebar=None,\n",
    "          center=center_button,\n",
    "          right_sidebar=right_button,\n",
    "          footer=footer_button)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "AppLayout(header=header_button,\n",
    "          left_sidebar=None,\n",
    "          center=center_button,\n",
    "          right_sidebar=None,\n",
    "          footer=footer_button)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "AppLayout(header=header_button,\n",
    "          left_sidebar=left_button,\n",
    "          center=None,\n",
    "          right_sidebar=right_button,\n",
    "          footer=footer_button)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can also modify the relative and absolute widths and heights of the panes using `pane_widths` and `pane_heights` arguments. Both accept a sequence of three elements, each of which is either an integer (equivalent to the weight given to the row/column) or a string in the format `'1fr'` (same as integer) or `'100px'` (absolute size)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "AppLayout(header=header_button,\n",
    "          left_sidebar=left_button,\n",
    "          center=center_button,\n",
    "          right_sidebar=right_button,\n",
    "          footer=footer_button,\n",
    "          pane_widths=[3, 3, 1],\n",
    "          pane_heights=[1, 5, '60px'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Grid layout"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`GridspecLayout` is a N-by-M grid layout allowing for flexible layout definitions using an API similar to matplotlib's [GridSpec](https://matplotlib.org/tutorials/intermediate/gridspec.html#sphx-glr-tutorials-intermediate-gridspec-py).\n",
    "\n",
    "You can use `GridspecLayout` to define a simple regularly-spaced grid. For example, to create a 4x3 layout:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from ipywidgets import GridspecLayout\n",
    "\n",
    "grid = GridspecLayout(4, 3)\n",
    "\n",
    "for i in range(4):\n",
    "    for j in range(3):\n",
    "        grid[i, j] = create_expanded_button('Button {} - {}'.format(i, j), 'warning')\n",
    "grid"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To make a widget span several columns and/or rows, you can use slice notation:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "grid = GridspecLayout(4, 3, height='300px')\n",
    "grid[:3, 1:] = create_expanded_button('One', 'success')\n",
    "grid[:, 0] = create_expanded_button('Two', 'info')\n",
    "grid[3, 1] = create_expanded_button('Three', 'warning')\n",
    "grid[3, 2] = create_expanded_button('Four', 'danger')\n",
    "grid"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can still change properties of the widgets stored in the grid, using the same indexing notation.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "grid = GridspecLayout(4, 3, height='300px')\n",
    "grid[:3, 1:] = create_expanded_button('One', 'success')\n",
    "grid[:, 0] = create_expanded_button('Two', 'info')\n",
    "grid[3, 1] = create_expanded_button('Three', 'warning')\n",
    "grid[3, 2] = create_expanded_button('Four', 'danger')\n",
    "\n",
    "grid"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "grid[0, 0].description = \"I am the blue one\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "**Note**: It's enough to pass an index of one of the grid cells occupied by the widget of interest. Slices are not supported in this context."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If there is already a widget that conflicts with the position of the widget being added, it will be removed from the grid:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "grid = GridspecLayout(4, 3, height='300px')\n",
    "grid[:3, 1:] = create_expanded_button('One', 'info')\n",
    "grid[:, 0] = create_expanded_button('Two', 'info')\n",
    "grid[3, 1] = create_expanded_button('Three', 'info')\n",
    "grid[3, 2] = create_expanded_button('Four', 'info')\n",
    "\n",
    "grid"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "grid[3, 1] = create_expanded_button('New button!!', 'danger')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Note**: Slices are supported in this context."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "grid[:3, 1:] = create_expanded_button('I am new too!!!!!', 'warning')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Creating scatter plots using GridspecLayout"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In this examples, we will demonstrate how to use `GridspecLayout` and `bqplot` widget to create a multipanel scatter plot. To run this example you will need to install the [bqplot](https://bqplot.readthedocs.io/en/latest/) package."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "For example, you can use the following snippet to obtain a scatter plot across multiple dimensions:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import bqplot as bq\n",
    "import numpy as np\n",
    "from ipywidgets import GridspecLayout, Button, Layout\n",
    "\n",
    "n_features = 5\n",
    "data = np.random.randn(100, n_features)\n",
    "data[:50, 2] += 4 * data[:50, 0] **2\n",
    "data[50:, :] += 4\n",
    "\n",
    "A = np.random.randn(n_features, n_features)/5\n",
    "\n",
    "data = np.dot(data,A)\n",
    "\n",
    "scales_x = [bq.LinearScale() for i in range(n_features)]\n",
    "scales_y = [bq.LinearScale() for i in range(n_features)]\n",
    "\n",
    "gs = GridspecLayout(n_features, n_features)\n",
    "for i in range(n_features):\n",
    "    for j in range(n_features):\n",
    "        \n",
    "        if i != j:\n",
    "            sc_x = scales_x[j]\n",
    "            sc_y = scales_y[i]\n",
    "\n",
    "            scatt = bq.Scatter(x=data[:, j], y=data[:, i], scales={'x': sc_x, 'y': sc_y}, default_size=1)\n",
    "\n",
    "            gs[i, j] = bq.Figure(marks=[scatt], layout=Layout(width='auto', height='auto'),\n",
    "                                 fig_margin=dict(top=0, bottom=0, left=0, right=0))\n",
    "        else:\n",
    "            sc_x = scales_x[j]\n",
    "            sc_y = bq.LinearScale()\n",
    "        \n",
    "            hist = bq.Hist(sample=data[:,i], scales={'sample': sc_x, 'count': sc_y})\n",
    "            \n",
    "            gs[i, j] = bq.Figure(marks=[hist], layout=Layout(width='auto', height='auto'),\n",
    "                                 fig_margin=dict(top=0, bottom=0, left=0, right=0))\n",
    "gs"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Style attributes"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can specify extra style properties to modify the layout. For example, you can change the size of the whole layout using the `height` and `width` arguments."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "AppLayout(header=None,\n",
    "          left_sidebar=left_button,\n",
    "          center=center_button,\n",
    "          right_sidebar=right_button,\n",
    "          footer=None,\n",
    "          height=\"200px\", width=\"50%\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The gap between the panes can be increase or decreased with `grid_gap` argument:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "AppLayout(header=None,\n",
    "          left_sidebar=left_button,\n",
    "          center=center_button,\n",
    "          right_sidebar=right_button,\n",
    "          footer=None,\n",
    "          height=\"200px\", width=\"50%\",\n",
    "          grid_gap=\"10px\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Additionally, you can control the alignment of widgets within the layout using `justify_content` and `align_items` attributes:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from ipywidgets import Text, HTML\n",
    "TwoByTwoLayout(top_left=top_left_button, top_right=top_right_button,\n",
    "               bottom_right=bottom_right_button,\n",
    "               justify_items='center',\n",
    "               width=\"50%\",\n",
    "               align_items='center')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "For other alignment options it's possible to use common names (`top` and `bottom`) or their CSS equivalents (`flex-start` and `flex-end`):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "TwoByTwoLayout(top_left=top_left_button, top_right=top_right_button,\n",
    "               bottom_right=bottom_right_button,\n",
    "               justify_items='center',\n",
    "               width=\"50%\",\n",
    "               align_items='top')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Example\n",
    "\n",
    "In this [notebook](Layout%20Example.ipynb) you will find a full example using `AppLayout`.\n",
    "\n",
    "![](images/applayout-weather.png)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
